package com.samuel.basics.aop;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.samuel.basics.annotation.CacheLock;
import com.samuel.basics.constant.CommonConstant;
import com.samuel.basics.exception.CustomException;
import com.samuel.basics.utils.UuidUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;

import javax.annotation.Resource;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * <p>
 * 	数值越小 执行越早<br/>
 * 	短时间内不能多次提交
 * </p>
 *
 * @author kaiji
 */
@Aspect
@Order(0)
@Configuration
public class LockMethodAop {

	private static final Logger logger = LoggerFactory.getLogger(LockMethodAop.class);
	
	private static final String DELIMITER = "|";
	// ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(10);
	private static final ScheduledThreadPoolExecutor THREAD_POOL_EXECUTOR;

	static {
		ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("lock-method-pool-%d").build();
		THREAD_POOL_EXECUTOR = new ScheduledThreadPoolExecutor(CommonConstant.MAGIC_VALUE_ONE, threadFactory);
	}
	@Resource
	private StringRedisTemplate stringRedisTemplate;
	
	@Before("@annotation(lock)")
	public void interceptor(JoinPoint pjp, CacheLock lock) {

		if (StringUtils.isBlank(lock.prefix())) {
			throw new RuntimeException("lock key don't null...");
		}

		final String lockKey = getLockKey(pjp, lock);
		final String uuid = UuidUtil.getUuid();
		try {
			// 假设上锁成功，但是设置过期时间失效，以后拿到的都是 false
			final boolean success = lock(lockKey, uuid, lock.expire(), lock.timeUnit());
			if (!success) {
				// MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
				// ResponseBody responseBody = methodSignature.getMethod().getAnnotation(ResponseBody.class);
				throw new CustomException(CommonConstant.ExceptionClazz.RESPONSE_ERROR_CODE, "您点击得太快了！", null);
			}
		} finally {
			unlock(lockKey, uuid);
		}
	}


	/**
	 * 获取key
	 * 
	 * @return string
	 */
	private String getLockKey(JoinPoint pjp, CacheLock cacheLock) {
		Object[] args = pjp.getArgs();
        StringBuilder builder = new StringBuilder();
        for (Object obj : args) {
        	builder.append(cacheLock.delimiter()).append(obj);
		}
        return cacheLock.prefix() + builder.toString();
	}


	/**
     * 获取锁
     *
     * @param lockKey lockKey
     * @param uuid    UUID
     * @param timeout 超时时间
     * @param unit    过期单位
     * @return true or false
     */
	private Boolean lock(String lockKey, String uuid, int timeout, TimeUnit unit) {
		final long milliseconds = Expiration.from(timeout, unit).getExpirationTimeInMilliseconds();
		Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, (System.currentTimeMillis() + milliseconds) + DELIMITER + uuid);
        if (success != null && success) {
			stringRedisTemplate.expire(lockKey, timeout, TimeUnit.SECONDS);
			return true;
        }
		String oldVal = stringRedisTemplate.opsForValue().getAndSet(lockKey, (System.currentTimeMillis() + milliseconds) + DELIMITER + uuid);
		assert oldVal != null;
		final String[] oldValues = oldVal.split(Pattern.quote(DELIMITER));
		return Long.parseLong(oldValues[0]) + 1 <= System.currentTimeMillis();
	}
	
	/**
     * 默认2S后删除
     */
    public void unlock(String lockKey, String value) {
        unlock(lockKey, value, CommonConstant.MAGIC_VALUE_TWO, TimeUnit.SECONDS);
    }

    /**
     * 延迟unlock
     *
     * @param lockKey   key
     * @param uuid      client(最好是唯一键的)
     * @param delayTime 延迟时间
     * @param unit      时间单位
     */
    public void unlock(final String lockKey, final String uuid, long delayTime, TimeUnit unit) {
        if (StringUtils.isBlank(lockKey)) {
            return;
        }
        if (delayTime <= 0) {
            doUnlock(lockKey, uuid);
        } else {
            /*
            1. 第一种
            EXECUTOR_SERVICE.schedule(() -> doUnlock(lockKey, uuid), delayTime, unit);

			EXECUTOR_SERVICE.schedule(new Runnable() {
				@Override
				public void run() {
					doUnlock(lockKey, uuid);
				}
			}, delayTime, unit);
			*/
			try {
				// 第二种
				// int corePoolSize = CommonConstant.MAGIC_VALUE_ONE;
				// int maximumPollSize = 10;
				// long keepAliveTime = 15L;
				// BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
				// ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPollSize, keepAliveTime, unit, workQueue, threadFactory);

				// threadPoolExecutor.execute(() -> doUnlock(lockKey, uuid));
				// 第三种
				THREAD_POOL_EXECUTOR.schedule(()->doUnlock(lockKey, uuid), delayTime, unit);
			} catch (Exception e) {
				logger.error("进程异常！原因：{}", e.getMessage());
			}

        }
    }
    
    /**
     * @param lockKey key
     * @param uuid    client(最好是唯一键的)
     */
    private void doUnlock(final String lockKey, final String uuid) {
        String val = stringRedisTemplate.opsForValue().get(lockKey);
		if (val == null) {
			return;
		}
		final String[] values = val.split(Pattern.quote(DELIMITER));
        if (values.length <= 0) {
            return;
        }
        if (uuid.equals(values[1])) {
			stringRedisTemplate.delete(lockKey);
        }
    }
}
